/*
 * Copyright 2002-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.fjsei.yewu.inspect;

import com.alibaba.fastjson2.JSON;
import com.querydsl.core.BooleanBuilder;
import graphql.relay.Connection;
import graphql.schema.DataFetchingEnvironment;
import md.cm.base.Companies;
import md.cm.flow.ApprovalStmRepository;
import md.cm.geography.Village;
import md.cm.unit.Division;
import md.cm.unit.Unit;
import md.cm.unit.Units;
import md.specialEqp.Eqp;
import md.specialEqp.Report;
import md.specialEqp.ReportRepository;
import md.specialEqp.inspect.*;
import md.specialEqp.type.Elevator;
import md.specialEqp.type.PipingUnit;
import md.specialEqp.type.Vessel;
import md.system.User;
import org.fjsei.yewu.entity.fjtj.UntMge;
import org.fjsei.yewu.graphql.DbPageConnection;
import org.fjsei.yewu.graphql.IdMapper;
import org.fjsei.yewu.input.IspCommonInput;
import org.fjsei.yewu.jpa.PageOffsetFirst;
import org.fjsei.yewu.pojo.jsn.ElevatorSvp;
import org.fjsei.yewu.pojo.sei.DeviceSnapshot;
import org.fjsei.yewu.resolver.Comngrql;
import org.fjsei.yewu.util.Tool;
import org.hibernate.Hibernate;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.execution.BatchLoaderRegistry;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.List;
import java.util.Set;

/**默认Unit模型的属性； graphQL接口;
 * 底下批处理方案还不是一种的。 注释掉的也是1个方案。
 * 每一个graphQL的Object对象type都需单独声明一个XxxController implements IdMapper<type> {} 否则Id都无法转换成Relay要的GlobalID的。
 * */

@Controller
public class DetailController extends Comngrql implements IdMapper<Detail> {
	@PersistenceContext(unitName = "entityManagerFactorySei")
	private EntityManager emSei;
	//private final Units units;
	private final Companies companies;
	private final TaskRepository tasks;
	private final IspRepository isps;
	//private final ApprovalStmRepository  approvalStms;
	private final ReportRepository  reports;
	private final DetailRepository  details;
	/** registry参数：IDEA报红却能运行成功，实际运行注入DefaultBatchLoaderRegistry;
     *  消除报红，测试平台Units没有报红 https://blog.csdn.net/zy103118/article/details/84523452
	 * 添加了 @ComponentScan({"md","org.fjsei.yewu"}) 以后: Units报红消除了。
	 * */
	public DetailController(BatchLoaderRegistry registry, Companies companies, TaskRepository tasks, IspRepository isps, ReportRepository reports, DetailRepository details) {
		this.companies = companies;
		this.tasks = tasks;
		this.isps = isps;
		this.reports = reports;
		this.details = details;
	}

	@Transactional(propagation= Propagation.REQUIRES_NEW)
	public String abandonDetailInner(Isp isp)
	{
		/* Isp不管了,？有些确实需要删除的, todo://根据Isp的状态判定，已经终结的报告Isp不能删除。
		 */
		int errCnt=0;
		if(null!=isp){
		//	isp.setBus(null);		//不能省掉 报错。
			//已经关联的Report的清理？
			if(!isp.getReps().isEmpty())	errCnt++;
			//   Assert.isTrue(isp.getReps().isEmpty(),"关联Report未清理");
			//特别的字段@ManyToMany需要清理掉： ispMen 解除关系
			//Set<User> mens= isp.getIspMen();
			//mens.forEach(a -> a.getIsp().remove(isp));
			//若不解除关系不能立即给正确的应答，在缓存期限时间内做关联查找会报错某关联id找不到。
			//下面这两行若去掉一个都会导致关联id找不到，除非cache缓存时间过了才行，或者其他操作影响。
			//isp.getTask().getIsps().remove(isp);
			//emSei.remove(isp);
			isps.delete(isp);		//应该是其他表关联了Isp,删除需要检查全部被对方eqp关联用到的情况，eqp表却对关联id字段没有做索引，导致巨慢！。
			//emSei.flush(); 加了更慢！
		}
		return  0==errCnt? "":"有1个关联的Report未清理";
	}
	/**删除某一个Detail检验业务记录
	 * 有些无效操作竟然没有报错的。
	 * 冷数据Isp相关尽量不作删除。相关性对象的关联拆除或者删除更深关联层次的。
	 * */
	@MutationMapping
	@Transactional
	public boolean abandonDetail(@Argument String detId,@Argument String reason)
	{
		Detail detail=entityOf(detId,Detail.class);
		Assert.isTrue(detail != null,"未找到Detail:"+detId);
		Isp isp=detail.getIsp();
		detail.setIsp(null);	//切断关联isp-detail: 加速？
		/* Isp不管了,？有些确实需要删除的, todo://根据Isp的状态判定，已经终结的报告Isp不能删除。
		 */
		String resp="";
		if(null!=isp){
			 resp= abandonDetailInner(isp);
		}
//		//清理关联的Charging? 收费计算信息清理。对方表有我放的ID存储着，报错！不能简单删除关联id;【麻烦】一个连接着另外一个id;连串清理。
//		//清理关联Charging.pipus<>
//		detail.getFees().forEach(a -> {
//			List<PipingUnit> pipingUnits=a.getPipus();
//			//Assert.isTrue(pipingUnits.isEmpty(),"关联Charging.pipus未清理");
//			//多对多@ManyToMany反而更容易清理：可自动删除掉中间表数据。
//			emSei.remove(a);
//		} );
//		//被引用的：管道单元锁要清除 pipus，不然就报错了。
//		detail.getPipus().forEach(a -> {
//			a.setDet(null);
//		} );
//		emSei.remove(detail);
		return  resp.length()==0;
	}

	/**当前我的检验业务： 不上ES搜索，查关系数据库，又没搞对索引：太慢了！
	 * 【综合考量应该怎么加索引】还没规划设置好索引，本查询特别的慢！很多查询语句也并不是都需要那么多的字段，Entity Projection没用上。不同语句使用的频率，权衡代价和获益。
	 * */
	@QueryMapping
	public Connection<Detail> findMeDetails(@Argument String orderBy, @Argument Boolean asc, @Argument IspCommonInput where, @Argument Integer first,
											@Argument String after, @Argument Integer last, @Argument String before, DataFetchingEnvironment env) {
		User user= checkAuth();
		if(user==null)   return null;
		DbPageConnection<Detail> connection=new DbPageConnection(env);
		int offset=connection.getOffset();
		int limit=connection.getLimit();
		Pageable pageable;
		if (!StringUtils.hasLength(orderBy))
			pageable = PageOffsetFirst.of(offset, limit);
		else
			pageable = PageOffsetFirst.of(offset, limit, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy));
		QDetail qm = QDetail.detail;
		BooleanBuilder builder = new BooleanBuilder();
		BooleanBuilder userbuild = new BooleanBuilder();
		//默认：在这个位置qm.isp.report.stm就会出现null 空指针(IDEA观察到)，但可能也根本就没提示的  直接歇菜。querydsl解决办法@QueryInit("report.stm.master")多对一一对一的字段才会出现此类问题。
		userbuild.or(qm.isp.report.stm.master.eq(user)).or(qm.isp.report.stm.authr.contains(user));
		BooleanBuilder stabuild = new BooleanBuilder();
		stabuild.or(qm.isp.report.stm.sta.eq(Procedure_Enum.BEGIN)).or(qm.isp.report.stm.sta.eq(Procedure_Enum.MAKE));
		builder.and(userbuild).and(stabuild);
		if(StringUtils.hasText(where.getServu())) {
			Unit unit=fromInputUnitGlobalID(where.getServu());
			Assert.notNull(unit,"未找到Unit:"+where.getServu());
			builder.and(qm.task.servu.id.eq(unit.getId()));
		}
		if(StringUtils.hasText(where.getCod()))
			builder.and(qm.isp.dev.cod.containsIgnoreCase(where.getCod()));
		if(StringUtils.hasText(where.getOid()))
			builder.and(qm.isp.dev.oid.containsIgnoreCase(where.getOid()));
		if(StringUtils.hasText(where.getIdent()))
			builder.and(qm.ident.containsIgnoreCase(where.getIdent()));
		if(StringUtils.hasText(where.getNo()))
			builder.and(qm.isp.no.containsIgnoreCase(where.getNo()));
		//【神奇了 querydsl】实际上这builder在这直接照抄findMeIsps(){}里面的竟然也可以！ 会从QIsp QDetail自动生成关联关系的。我都没定义QDetail的必要了。
		Slice<Detail> rpage= (Slice<Detail>)details.findAll(builder,pageable);
		List<Detail> isps=(List<Detail>) rpage.toList();
		return connection.setListData(isps).get(env);
	}

	/**影响范围：当前Isp的报告的snapshot字段{}填最新的台账信息。分项报告同样一起变。
	 * 返回 主报告
	 * */
	@MutationMapping
	@Transactional
	public Report reportFillEqpField(@Argument("detId") String ispId)
	{
//		Detail detail=entityOf(detId,Detail.class);
		Isp isp=entityOf(ispId,Isp.class);
		Assert.notNull(isp,"未找到Isp:"+ispId);
		Report report =isp.getReport();
		Assert.notNull(report,"未找到主报告,isp="+isp.getId());
		DeviceSnapshot dss = new DeviceSnapshot();      //快照 做报告哪个时间点的数据
		if(null==isp.getDev()) {
			//Detail里面的定位信息字段 ident 导入？
			String strJson = JSON.toJSONString(dss);			//"{}"
			report.setSnapshot(strJson);
			if(!StringUtils.hasText(report.getData()))	 report.setData("{}");
			reports.save(report);
			return report;		//没挂接设备台账的独立报告。
		}
		Object unproxiedEntity = Hibernate.unproxy(isp.getDev());
		Eqp eqp= (Eqp) unproxiedEntity;
		dss.setEqpcod(eqp.getCod());
		dss.set监察识别码(eqp.getOid());
		dss.set使用证号(eqp.getCert());
		dss.set设备代码(eqp.getSno());
		//规范代码 替换汉语名称，前端自己映射转码显示中文名？
		dss.set设备类别(eqp.getSort());
		dss.set设备品种(eqp.getVart());        //品种  EQP_VART_NAME
		dss.set型号(eqp.getModel());
		dss.set出厂编号(eqp.getFno());
		dss.set单位内部编号(eqp.getPlno());
		dss.set制造日期(eqp.getMkd().toString());
		dss.set下检日期(null!=eqp.getNxtd2()? eqp.getNxtd2().toString() : "");
		dss.set设备使用地点(eqp.getAddress());
		Village village = eqp.getVlg();
		if (null != village) {
			dss.set楼盘(village.getName());
			dss.set楼盘地址(village.getAddress());    //前面行政地理描述部分要省略掉。
		}
		UntMge untMge = null;
		Unit mtu = eqp.getMtu();
		if (null != mtu) dss.set维保单位(mtu.name());
		if (null != eqp.getRemu()) dss.set改造单位(eqp.getRemu().name());
		if (null != eqp.getMakeu()) dss.set制造单位(eqp.getMakeu().name());
		Division division = eqp.getUsud();
		if (null != division) {
			dss.set分支机构(division.getName());		  	//.SECUDEPT_ID	'分支机构ID' || .SAFE_DEPT_ID '安全管理部门' mtp=2 :1?
			dss.set分支机构地址(division.getAddress());        //不要area也就是lare所对应的区划地址前缀；
		}
		Unit useu = eqp.getUseu();
		if (null != useu) {
			dss.set使用单位(useu.name());
			dss.set使用单位地址(useu.getAddress(null));		 		//graphQL接口 getAddress
		}
		//和特种设备类型相关的部分：
		//Class<?> devtype=report.getIsp().getDev().getClass();   不是Elevator而是Hibernate代理类。
		if(unproxiedEntity instanceof Elevator) {			//电梯的
			Elevator device = (Elevator) unproxiedEntity;
			dss.set控制方式(device.getOpm());
			dss.set电梯层数(device.getFlo().longValue());
			String oldsvp = eqp.getSvp();
			ElevatorSvp svp = JSON.parseObject(oldsvp, ElevatorSvp.class);
			dss.set电梯站数(svp.get电梯站数().longValue());
			dss.set电梯门数(svp.get电梯门数().longValue());
			dss.set运行速度(device.getVl().doubleValue());
			dss.set额定载荷(device.getRtl().doubleValue());
		}
		else if(unproxiedEntity instanceof Vessel) {		 	//容器的
			Vessel device = (Vessel) unproxiedEntity;
		}
		String strJson = JSON.toJSONString(dss);
		report.setSnapshot(strJson);
		//子报告分项报告没必要单独设置Snapshot字段，采用和主报告同一个Snapshot数据。同时保留不一样的可能性。继承主报告。
		if(!StringUtils.hasText(report.getData()))	 report.setData("{}");	    //前端允许报告data=null，但是snapshot不允许为空。
		reports.save(report);
		return report;
	}
}

